#pragma once
#include "CGITexture.h"
#include "ExclusiveDepthStencil.h"
#include "Runtime/Utils/SmartPtr.h"

namespace Alice
{
	struct FResolveRect
	{
		int32 X1;
		int32 Y1;
		int32 X2;
		int32 Y2;

		// e.g. for a a full 256 x 256 area starting at (0, 0) it would be 
		// the values would be 0, 0, 256, 256
		FResolveRect(int32 InX1 = -1, int32 InY1 = -1, int32 InX2 = -1, int32 InY2 = -1)
			: X1(InX1)
			, Y1(InY1)
			, X2(InX2)
			, Y2(InY2)
		{}

		bool operator==(FResolveRect Other) const
		{
			return X1 == Other.X1 && Y1 == Other.Y1 && X2 == Other.X2 && Y2 == Other.Y2;
		}

		bool operator!=(FResolveRect Other) const
		{
			return !(*this == Other);
		}

		bool IsValid() const
		{
			return X1 >= 0 && Y1 >= 0 && X2 - X1 > 0 && Y2 - Y1 > 0;
		}
	};
    class CGIRenderTargetView : public Object
	{
	public:
		CGITexture* Texture = nullptr;
		uint32 MipIndex = 0;

		/** Array slice or texture cube face.  Only valid if texture resource was created with TexCreate_TargetArraySlicesIndependently! */
		uint32 ArraySliceIndex = -1;

		ERenderTargetLoadAction LoadAction = ERenderTargetLoadAction::ENoAction;
		ERenderTargetStoreAction StoreAction = ERenderTargetStoreAction::ENoAction;

		CGIRenderTargetView() = default;
		CGIRenderTargetView(CGIRenderTargetView&&) = default;
		CGIRenderTargetView(const CGIRenderTargetView&) = default;
		CGIRenderTargetView& operator=(CGIRenderTargetView&&) = default;
		CGIRenderTargetView& operator=(const CGIRenderTargetView&) = default;

		//common case
		explicit CGIRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InLoadAction) :
			Texture(InTexture),
			MipIndex(0),
			ArraySliceIndex(-1),
			LoadAction(InLoadAction),
			StoreAction(ERenderTargetStoreAction::EStore)
		{}

		//common case
		explicit CGIRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InLoadAction, uint32 InMipIndex, uint32 InArraySliceIndex) :
			Texture(InTexture),
			MipIndex(InMipIndex),
			ArraySliceIndex(InArraySliceIndex),
			LoadAction(InLoadAction),
			StoreAction(ERenderTargetStoreAction::EStore)
		{}
	
		explicit CGIRenderTargetView(CGITexture* InTexture, uint32 InMipIndex, uint32 InArraySliceIndex, ERenderTargetLoadAction InLoadAction, ERenderTargetStoreAction InStoreAction) :
			Texture(InTexture),
			MipIndex(InMipIndex),
			ArraySliceIndex(InArraySliceIndex),
			LoadAction(InLoadAction),
			StoreAction(InStoreAction)
		{}

		bool operator==(const CGIRenderTargetView& Other) const
		{
			return 
				Texture == Other.Texture &&
				MipIndex == Other.MipIndex &&
				ArraySliceIndex == Other.ArraySliceIndex &&
				LoadAction == Other.LoadAction &&
				StoreAction == Other.StoreAction;
		}
	};
	class CGIDepthRenderTargetView
	{
	public:
		CGITexture* Texture;

		ERenderTargetLoadAction		DepthLoadAction;
		ERenderTargetStoreAction	DepthStoreAction;
		ERenderTargetLoadAction		StencilLoadAction;

	private:
		ERenderTargetStoreAction	StencilStoreAction;
		ExclusiveDepthStencil		DepthStencilAccess;
	public:

		// accessor to prevent write access to StencilStoreAction
		ERenderTargetStoreAction GetStencilStoreAction() const { return StencilStoreAction; }
		// accessor to prevent write access to DepthStencilAccess
		ExclusiveDepthStencil GetDepthStencilAccess() const { return DepthStencilAccess; }

		explicit CGIDepthRenderTargetView() :
			Texture(nullptr),
			DepthLoadAction(ERenderTargetLoadAction::ENoAction),
			DepthStoreAction(ERenderTargetStoreAction::ENoAction),
			StencilLoadAction(ERenderTargetLoadAction::ENoAction),
			StencilStoreAction(ERenderTargetStoreAction::ENoAction),
			DepthStencilAccess(ExclusiveDepthStencil::DepthNop_StencilNop)
		{
			Validate();
		}

		//common case
		explicit CGIDepthRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InLoadAction, ERenderTargetStoreAction InStoreAction) :
			Texture(InTexture),
			DepthLoadAction(InLoadAction),
			DepthStoreAction(InStoreAction),
			StencilLoadAction(InLoadAction),
			StencilStoreAction(InStoreAction),
			DepthStencilAccess(ExclusiveDepthStencil::DepthWrite_StencilWrite)
		{
			Validate();
		}

		explicit CGIDepthRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InLoadAction, ERenderTargetStoreAction InStoreAction, ExclusiveDepthStencil InDepthStencilAccess) :
			Texture(InTexture),
			DepthLoadAction(InLoadAction),
			DepthStoreAction(InStoreAction),
			StencilLoadAction(InLoadAction),
			StencilStoreAction(InStoreAction),
			DepthStencilAccess(InDepthStencilAccess)
		{
			Validate();
		}

		explicit CGIDepthRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InDepthLoadAction, ERenderTargetStoreAction InDepthStoreAction, ERenderTargetLoadAction InStencilLoadAction, ERenderTargetStoreAction InStencilStoreAction) :
			Texture(InTexture),
			DepthLoadAction(InDepthLoadAction),
			DepthStoreAction(InDepthStoreAction),
			StencilLoadAction(InStencilLoadAction),
			StencilStoreAction(InStencilStoreAction),
			DepthStencilAccess(ExclusiveDepthStencil::DepthWrite_StencilWrite)
		{
			Validate();
		}

		explicit CGIDepthRenderTargetView(CGITexture* InTexture, ERenderTargetLoadAction InDepthLoadAction, ERenderTargetStoreAction InDepthStoreAction, ERenderTargetLoadAction InStencilLoadAction, ERenderTargetStoreAction InStencilStoreAction, ExclusiveDepthStencil InDepthStencilAccess) :
			Texture(InTexture),
			DepthLoadAction(InDepthLoadAction),
			DepthStoreAction(InDepthStoreAction),
			StencilLoadAction(InStencilLoadAction),
			StencilStoreAction(InStencilStoreAction),
			DepthStencilAccess(InDepthStencilAccess)
		{
			Validate();
		}

		void Validate() const
		{
			// VK and Metal MAY leave the attachment in an undefined state if the StoreAction is DontCare. So we can't assume read-only implies it should be DontCare unless we know for sure it will never be used again.
			// ensureMsgf(DepthStencilAccess.IsDepthWrite() || DepthStoreAction == ERenderTargetStoreAction::ENoAction, TEXT("Depth is read-only, but we are performing a store.  This is a waste on mobile.  If depth can't change, we don't need to store it out again"));
			/*ensureMsgf(DepthStencilAccess.IsStencilWrite() || StencilStoreAction == ERenderTargetStoreAction::ENoAction, TEXT("Stencil is read-only, but we are performing a store.  This is a waste on mobile.  If stencil can't change, we don't need to store it out again"));*/
		}

		bool operator==(const CGIDepthRenderTargetView& Other) const
		{
			return
				Texture == Other.Texture &&
				DepthLoadAction == Other.DepthLoadAction &&
				DepthStoreAction == Other.DepthStoreAction &&
				StencilLoadAction == Other.StencilLoadAction &&
				StencilStoreAction == Other.StencilStoreAction &&
				DepthStencilAccess == Other.DepthStencilAccess;
		}
	};
    class CGISetRenderTargetsInfo
	{
	public:
		// Color Render Targets Info
		SmartPtr<CGIRenderTargetView> mColorRenderTarget[MaxSimultaneousRenderTargets];	
		int32 NumColorRenderTargets;
		bool bClearColor;

		// Color Render Targets Info
		CGIRenderTargetView ColorResolveRenderTarget[MaxSimultaneousRenderTargets];	
		bool bHasResolveAttachments;

		// Depth/Stencil Render Target Info
		CGIDepthRenderTargetView DepthStencilRenderTarget;	
		// Used when depth resolve is enabled.
		CGIDepthRenderTargetView DepthStencilResolveRenderTarget;
		bool bClearDepth;
		bool bClearStencil;

		CGITexture* ShadingRateTexture;
		EVRSRateCombiner ShadingRateTextureCombiner;

		uint8 MultiViewCount;

		CGISetRenderTargetsInfo() :
			NumColorRenderTargets(0),
			bClearColor(false),
			bHasResolveAttachments(false),
			bClearDepth(false),
			ShadingRateTexture(nullptr),
			MultiViewCount(0)
		{}

		CGISetRenderTargetsInfo(int32 InNumColorRenderTargets, CGIRenderTargetView* InColorRenderTargets, const CGIDepthRenderTargetView& InDepthStencilRenderTarget) :
			NumColorRenderTargets(InNumColorRenderTargets),
			bClearColor(InNumColorRenderTargets > 0 && InColorRenderTargets[0].LoadAction == ERenderTargetLoadAction::EClear),
			bHasResolveAttachments(false),
			DepthStencilRenderTarget(InDepthStencilRenderTarget),		
			bClearDepth(InDepthStencilRenderTarget.Texture && InDepthStencilRenderTarget.DepthLoadAction == ERenderTargetLoadAction::EClear),
			ShadingRateTexture(nullptr),
			ShadingRateTextureCombiner(VRSRB_Passthrough)
		{
			for (int32 Index = 0; Index < InNumColorRenderTargets; ++Index)
			{
				//mColorRenderTarget[Index] = InColorRenderTargets[Index];			
			}
		}
		// @todo metal mrt: This can go away after all the cleanup is done
		void SetClearDepthStencil(bool bInClearDepth, bool bInClearStencil = false)
		{
			if (bInClearDepth)
			{
				DepthStencilRenderTarget.DepthLoadAction = ERenderTargetLoadAction::EClear;
			}
			if (bInClearStencil)
			{
				DepthStencilRenderTarget.StencilLoadAction = ERenderTargetLoadAction::EClear;
			}
			bClearDepth = bInClearDepth;		
			bClearStencil = bInClearStencil;		
		}

		uint32 CalculateHash() const
		{
			// Need a separate struct so we can memzero/remove dependencies on reference counts
			struct FHashableStruct
			{
				// *2 for color and resolves
				// depth goes in the third-to-last slot
				// depth resolve goes in the second-to-last slot
				// shading rate goes in the last slot
				CGITexture* Texture[MaxSimultaneousRenderTargets*2 + 3];
				uint32 MipIndex[MaxSimultaneousRenderTargets];
				uint32 ArraySliceIndex[MaxSimultaneousRenderTargets];
				ERenderTargetLoadAction LoadAction[MaxSimultaneousRenderTargets];
				ERenderTargetStoreAction StoreAction[MaxSimultaneousRenderTargets];

				ERenderTargetLoadAction		DepthLoadAction;
				ERenderTargetStoreAction	DepthStoreAction;
				ERenderTargetLoadAction		StencilLoadAction;
				ERenderTargetStoreAction	StencilStoreAction;
				ExclusiveDepthStencil		DepthStencilAccess;

				bool bClearDepth;
				bool bClearStencil;
				bool bClearColor;
				bool bHasResolveAttachments;
				//FRHIUnorderedAccessView* UnorderedAccessView[MaxSimultaneousUAVs];
				uint8 MultiViewCount;

				void Set(const CGISetRenderTargetsInfo& RTInfo)
				{
					//FMemory::Memzero(*this);
					for (int32 Index = 0; Index < RTInfo.NumColorRenderTargets; ++Index)
					{
						Texture[Index] = RTInfo.mColorRenderTarget[Index]->Texture;
						Texture[MaxSimultaneousRenderTargets+Index] = RTInfo.ColorResolveRenderTarget[Index].Texture;
						MipIndex[Index] = RTInfo.mColorRenderTarget[Index]->MipIndex;
						ArraySliceIndex[Index] = RTInfo.mColorRenderTarget[Index]->ArraySliceIndex;
						LoadAction[Index] = RTInfo.mColorRenderTarget[Index]->LoadAction;
						StoreAction[Index] = RTInfo.mColorRenderTarget[Index]->StoreAction;
					}

					Texture[MaxSimultaneousRenderTargets*2] = RTInfo.DepthStencilRenderTarget.Texture;
					Texture[MaxSimultaneousRenderTargets*2 + 1] = RTInfo.DepthStencilResolveRenderTarget.Texture;
					Texture[MaxSimultaneousRenderTargets*2 + 2] = RTInfo.ShadingRateTexture;
					DepthLoadAction = RTInfo.DepthStencilRenderTarget.DepthLoadAction;
					DepthStoreAction = RTInfo.DepthStencilRenderTarget.DepthStoreAction;
					StencilLoadAction = RTInfo.DepthStencilRenderTarget.StencilLoadAction;
					StencilStoreAction = RTInfo.DepthStencilRenderTarget.GetStencilStoreAction();
					DepthStencilAccess = RTInfo.DepthStencilRenderTarget.GetDepthStencilAccess();

					bClearDepth = RTInfo.bClearDepth;
					bClearStencil = RTInfo.bClearStencil;
					bClearColor = RTInfo.bClearColor;
					bHasResolveAttachments = RTInfo.bHasResolveAttachments;
					MultiViewCount = RTInfo.MultiViewCount;
				}
			};

			FHashableStruct RTHash;
			//FMemory::Memzero(RTHash);
			RTHash.Set(*this);
			return 0;//FCrc::MemCrc32(&RTHash, sizeof(RTHash));
		}
	};
}
